package net.danielthompson.danray.cameras;

import net.danielthompson.danray.structures.Constants;
import net.danielthompson.danray.structures.Point;
import net.danielthompson.danray.structures.Vector;

/**
 * Created by daniel on 1/18/14.
 */
public class SimplePointableCamera implements Camera {

   /**
    * Width of the image in pixels.
    */
   private int _width;

   /**
    * Height of the image in pixels.
    */
   private int _height;

   private Point xDir = new Point(1, 0, 0);
   private Point yDir = new Point(0, 1, 0);
   private Point zDir = new Point(0, 0, 1);

   private double _rotation;

   private int _focalLength;
   private double _zoomFactor;
   private Vector _orientation;
   private Point _rearFocalPoint;

   private Point _implicitDirection = new Point(0, 0, -1);

   /**
    * Creates a new simple camera.
    * @param X Width of the image in pixels.
    * @param Y Height of the image in pixels.
    * @param focalLength Desired focal length.
    * @param orientation The location of the center of the image and the direction it's pointing.
    */
   public SimplePointableCamera(int X, int Y, int focalLength, double zoomFactor, Vector orientation) {

      this(X, Y, focalLength, 0.0, zoomFactor, orientation);
   }

   /**
    * Creates a new simple camera.
    * @param X Width of the image in pixels.
    * @param Y Height of the image in pixels.
    * @param focalLength Desired focal length.
    * @param orientation The location of the center of the image and the direction it's pointing.
    */
   public SimplePointableCamera(int X, int Y, int focalLength, double rotation, double zoomFactor, Vector orientation) {

      _width = X;
      _height = Y;
      _focalLength = focalLength;
      _rotation = rotation;
      _zoomFactor = zoomFactor;
      _orientation = orientation;

      _rearFocalPoint = Point.Scale(orientation.Direction, -_focalLength);
      _rearFocalPoint.Plus(orientation.Origin);
   }

   @Override
   public Point GetWorldPointForPixel(int x, int y) {
      return GetWorldPointForPixel((double)x, (double)y);
   }

   @Override
   public Point GetWorldPointForPixel(double x, double y) {
      // we define the absolute midpoint of the camera's screen to be at _orientation.location.
      // we arbitrarily assume that our camera is pointed at [0, 0, -1] -> straight down the z axis.

      // point is defined to be the midpoint of the camera in x and y: NDC (.5, .5):
      //Point point = new Point(_orientation.Origin.getX(), _orientation.Origin.getY(), _orientation.Origin.getZ());

      // first, find the world coordinate for the given pixel with this default camera orientation:
      //int offsetX =

      double Cx = ( _orientation.Origin.getX() + _zoomFactor * (x - (double)_width * .5));
      double Cy = ( _orientation.Origin.getY() + _zoomFactor * ((double)_height * .5 - y));
      double Cz = _orientation.Origin.getZ();

      Point point = new Point(Cx, Cy, Cz);



      double dot = _orientation.Direction.Dot(_implicitDirection);

      if (!Constants.WithinDelta(dot, 1))
      {
         Point rotationDirection = _orientation.Direction.Cross(zDir);
         rotationDirection.Normalize();
         Vector rotationAxis = new Vector(_orientation.Origin, rotationDirection);

         double theta = Math.toDegrees(Math.acos(_orientation.Direction.Dot(_implicitDirection)));

         point.Rotate(rotationAxis, theta);
         point.Rotate(_orientation, _rotation);
      }

      return point;
   }

   @Override
   public Vector[] GetInitialStochasticRaysForPixel(double x, double y, int SamplesPerPixel) {
      return new Vector[0];
   }

   @Override
   public Vector[] GetInitialStochasticRaysForPixel(int x, int y, int SamplesPerPixel) {


      Vector[] vectors = new Vector[SamplesPerPixel * SamplesPerPixel];

      int vectorPointer = 0;

      for (double i = 0.0; i < SamplesPerPixel; i++) {

         double newX = x + ((i + 1.0) / ((double)SamplesPerPixel + 1.0));


         for (double j = 0.0; j < SamplesPerPixel; j++) {
            double newY = y + ((j + 1.0) / ((double)SamplesPerPixel + 1.0));

            vectors[vectorPointer++] = new Vector(_rearFocalPoint, Point.Minus(GetWorldPointForPixel(newX, newY), _rearFocalPoint));
         }
      }

      return vectors;
   }

   @Override
   public Vector GetStochasticRayForPixel(double x, double y) {
      return null;
   }
}